﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;

namespace PonGun
{
    public delegate void UIEvent(object sender);
    public delegate void CollisionEvent(CollisionData collisionData);
    public delegate void ObjectCallBack(Base2DObject obj);

    public class Base2DObject
    {
        public Color[] TextureMap;
        static int Count;
        public Rectangle overlapRect = Rectangle.Empty;

        protected double uniqueRND;

        private int IDX;
        public string ID { get { return string.Format("{0}:{1}", this.GetType(), IDX); } }
        public Game1 Game;

        public virtual Vector2 minPosition { get { return new Vector2(Game1.RenderArea.Left + halfWidth, Game1.RenderArea.Top + halfHeight); } }
        public virtual Vector2 maxPosition { get { return new Vector2(Game1.RenderArea.Right - halfWidth, Game1.RenderArea.Bottom - halfHeight); } }

        public string Name { get; set; }
        private Vector2 _Position { get; set; }

        protected CollisionManager collisionManager { get { return (CollisionManager)Game.Services.GetService(typeof(CollisionManager)); } }

        public Vector2 Position
        {
            get
            {
                return _Position;
            }
            set
            {
                if (value != _Position)
                {
                    _Position = value;
                    collisionManager.UpdateObject(this);
                }
                else
                    _Position = value;
            }
        }

        public string Asset { get; set; }
        public Color Color { get; set; }
        public Vector2 Origin { get { return new Vector2(halfTextureWidth, halfTextureHeight); } }
        public Rectangle RenderRectangle { get { return new Rectangle((int)Position.X, (int)Position.Y, Width, Height); } }
        public Rectangle Bounds { get { return new Rectangle((int)Position.X - halfWidth, (int)Position.Y - halfHeight, Width, Height); } }

        public int halfWidth { get { return Width == 0 ? 0 : Width / 2; } }
        public int halfHeight { get { return Height == 0 ? 0 : Height / 2; } }

        private int _Width { get; set; }
        public int Width
        {
            get { return _Width; }
            set
            {
                if (_Width != value)
                    collisionManager.UpdateObject(this);

                _Width = value;
            }
        }
        private int _Height { get; set; }
        public int Height
        {
            get { return _Height; }
            set
            {
                if (_Height != value)
                    collisionManager.UpdateObject(this);

                _Height = value;
            }
        }
        public float LayerDepth { get; set; }

        public Vector2 ShadowHeight { get; set; }
        public Color ShadowColor { get { return new Color(0, 0, 0, .25f); } }

        public float Rotation { get; set; }

        protected Texture2D _Texture2D { get; set; }
        public Texture2D Texture2D
        {
            get
            {
                if (_Texture2D == null)
                    ReSetTexture2D();

                return _Texture2D;
            }
            set
            {
                _Texture2D = value;
            }
        }

        protected float halfTextureWidth { get { return Texture2D.Width * .5f; } }
        protected float halfTextureHeight { get { return Texture2D.Height * .5f; } }

        public CollisionEvent OnCollided;

        public Vector2 Velocity { get; set; }

        public float Friction { get; set; }

        public bool RegisteredCollider { get; set; }
        public bool Registered = false;

        public Base2DObject(Game1 game)
        {
            uniqueRND = Game1.rnd.NextDouble();
            Count++;
            IDX = Count;
            Game = game;
            Position = Vector2.Zero;
            Color = Color.White;
            Asset = "Textures/white";

            ShadowHeight = Vector2.Zero;
        }

        public virtual void Update(GameTime gameTime)
        {
            if (RegisteredCollider && !Registered)
                collisionManager.UpdateObject(this);

            Position += Velocity;

            Velocity *= 1 - Friction;

            if (RegisteredCollider && Registered && (TextureMap != null &&  TextureMap.Where(entity => entity.A != 0).ToArray().Length == 0))
            {
                collisionManager.UnRegister(this);
                RegisteredCollider = false;
            }
        }

        public virtual void ReSetTexture2D()
        {
            Texture2D = Game.Content.Load<Texture2D>(Asset);
            if (Texture2D.Width == 1 && Texture2D.Height == 1)
            {
                Color[] data = new Color[Texture2D.Width * Texture2D.Height];
                Texture2D.GetData<Color>(data);

                Texture2D = new Texture2D(Game.GraphicsDevice, Width, Height);
                TextureMap = new Color[Width * Height];

                for (int x = 0; x < Width; x++)
                    for (int y = 0; y < Height; y++)
                        TextureMap[x + y * Width] = new Color(data[0].ToVector4());

                Texture2D.SetData<Color>(TextureMap);
            }
            else
            {

                TextureMap = new Color[Texture2D.Width * Texture2D.Height];
                Texture2D.GetData<Color>(TextureMap);

                Texture2D = new Texture2D(Game.GraphicsDevice, Texture2D.Width, Texture2D.Height);
                Texture2D.SetData<Color>(TextureMap);
            }
        }

        public void ReSize(int newWidth = -1, int newHeight = -1)
        {
            if (newWidth > 0)
                Width = newWidth;
            else
                Width = 1;

            if (newHeight > 0)
                Height = newHeight;
            else
                Height = 1;

            ReSetTexture2D();
        }

        public void Translate(Vector2 direction, float speed)
        {
            Position += direction * speed;
        }

        public virtual Vector2 GetCollisionNormal(Base2DObject obj)
        {
            Vector2 contP = GetContactPosition(obj);

            //Vector2 dir = obj.Position - Position;
            Vector2 normal = Vector2.Zero;

            // If obj is between top an bottom, it must be a side normal

            if (obj.Bounds.Center.X  <= Bounds.X + overlapRect.Width)
                normal = Game1.rightNormal;

            if (obj.Bounds.Center.X >= Bounds.X + Bounds.Width - overlapRect.Width)
                normal = Game1.leftNormal;

            if (obj.Bounds.Center.Y >= Bounds.Y + Bounds.Height - overlapRect.Height)
                normal = Game1.topNormal;

            if (obj.Bounds.Center.Y <= Bounds.Y + overlapRect.Height)
                normal = Game1.botNormal;

            if (normal == Vector2.Zero)
            {
                if (obj.Bounds.X + obj.Bounds.Width <= Bounds.Center.X)
                    normal = Game1.rightNormal;

                if (obj.Bounds.X >= Bounds.Center.X)
                    normal = Game1.leftNormal;

                if (obj.Bounds.Y >= Bounds.Center.Y)
                    normal = Game1.topNormal;

                if (obj.Bounds.Y + obj.Bounds.Height <= Bounds.Center.Y)
                    normal = Game1.botNormal;
            }

            if (normal == Vector2.Zero && (Position.X != obj.Position.X && Position.Y != obj.Position.Y))
            {
                normal = Vector2.Normalize(obj.Velocity);
            }

            return normal;
        }

        public virtual Vector2 GetContactPosition(Base2DObject obj)
        {
            int mw = (int)MathHelper.Min(Width, obj.Width);
            int mh = (int)MathHelper.Min(Height, obj.Height);

            // Are any bits of obj covering one of my A != 0 pixels?
            int xColC = 0;
            int yRowC = 0;

            int xColE = 0;
            int yRowE = 0;

            Vector2 dir = obj.Position - Position;

            #region X
            if (dir.X > 0)
            {
                xColC = Width - (int)((Position.X + halfWidth) - (obj.Position.X - obj.halfWidth))-1;
                xColE = (int)MathHelper.Clamp((Position.X + halfWidth) - (obj.Position.X - obj.halfWidth) + 1, 1, mw);
            }

            if (dir.X < 0)
            {
                xColC = (int)MathHelper.Clamp((obj.Position.X - obj.halfWidth) - (Position.X - halfWidth), 0, Width);                
                xColE = (int)MathHelper.Clamp((obj.Position.X + obj.halfWidth) - (Position.X - halfWidth), 1, mw);
            }

            if (dir.X == 0)
            {
                xColC = halfWidth - obj.halfWidth;
                xColE = mw;

            }

            #endregion

            #region Y
            if (dir.Y > 0)
            {
                yRowC = Height - (int)((Position.Y + halfHeight) - (obj.Position.Y - obj.halfHeight)) - 1;
                yRowE = (int)MathHelper.Clamp((Position.Y + halfHeight) - (obj.Position.Y - obj.halfHeight) + 1, 1, mh);
            }

            if (dir.Y < 0)
            {
                yRowC = (int)MathHelper.Clamp((obj.Position.Y - obj.halfHeight) - (Position.Y - halfHeight), 0, Height);
                yRowE = (int)MathHelper.Clamp((obj.Position.Y + obj.halfHeight) - (Position.Y - halfHeight), 1, mh);
            }

            if (dir.Y == 0)
            {
                yRowC = halfHeight - obj.halfHeight;
                yRowE = mh;

            }

            #endregion

            if (xColC < 0)
                xColC = 0;

            if (yRowC < 0)
                yRowC = 0;

            if (yRowC - obj.halfHeight >= 0)
                yRowC -= obj.halfHeight;

            if (xColC - obj.halfWidth >= 0)
                xColC -= obj.halfWidth;

            Game.Window.Title = string.Format("{0} : {1} {2} - {3} dir = {4}", xColC, yRowC, xColE, yRowE,dir);

            if (xColE >= 0 && yRowE >= 0)
            {
                overlapRect = new Rectangle(xColC, yRowC, xColE, yRowE);
                Color[] targetMap = new Color[xColE * yRowE];
                Texture2D.GetData<Color>(0, overlapRect, targetMap, 0, targetMap.Length);

                overlapRect.X += (int)Position.X - halfWidth;
                overlapRect.Y += (int)Position.Y - halfHeight;
                if (targetMap.Count(entity => entity.A != 0) > 0)
                    return new Vector2(xColC, yRowC);
            }
            else
                overlapRect = Rectangle.Empty;
            
            return -Vector2.One;
        }

        public virtual bool Erroded(Base2DObject obj, bool errodBlock = false)
        {
            bool erroded = false;

            int mw = (int)MathHelper.Min(Width, obj.Width);
            int mh = (int)MathHelper.Min(Height, obj.Height);

            // Are any bits of obj covering one of my A != 0 pixels?
            int xColC = 0;
            int yRowC = 0;

            int xColE = 0;
            int yRowE = 0;

            Vector2 dir = obj.Position - Position;

            #region X
            if (dir.X > 0)
            {
                xColC = Width - (int)((Position.X + halfWidth) - (obj.Position.X - obj.halfWidth)) - 1;
                xColE = (int)MathHelper.Clamp((Position.X + halfWidth) - (obj.Position.X - obj.halfWidth) + 1, 1, mw);
            }

            if (dir.X < 0)
            {
                xColC = (int)MathHelper.Clamp((obj.Position.X - obj.halfWidth) - (Position.X - halfWidth), 0, Width);
                xColE = (int)MathHelper.Clamp((obj.Position.X + obj.halfWidth) - (Position.X - halfWidth), 1, mw);
            }

            if (dir.X == 0)
            {
                xColC = halfWidth - obj.halfWidth;
                xColE = mw;

            }

            #endregion

            #region Y
            if (dir.Y > 0)
            {
                yRowC = Height - (int)((Position.Y + halfHeight) - (obj.Position.Y - obj.halfHeight)) - 1;
                yRowE = (int)MathHelper.Clamp((Position.Y + halfHeight) - (obj.Position.Y - obj.halfHeight) + 1, 1, mh);
            }

            if (dir.Y < 0)
            {
                yRowC = (int)MathHelper.Clamp((obj.Position.Y - obj.halfHeight) - (Position.Y - halfHeight), 0, Height);
                yRowE = (int)MathHelper.Clamp((obj.Position.Y + obj.halfHeight) - (Position.Y - halfHeight), 1, mh);
            }

            if (dir.Y == 0)
            {
                yRowC = halfHeight - obj.halfHeight;
                yRowE = mh;

            }

            #endregion

            if (xColC < 0)
                xColC = 0;

            if (yRowC < 0)
                yRowC = 0;

            if (xColE >= 0 && yRowE >= 0)
            {
                Rectangle tmp = new Rectangle(xColC, yRowC, xColE, yRowE);
                Color[] targetMap = new Color[xColE * yRowE];
                Texture2D.GetData<Color>(0, tmp, targetMap, 0, targetMap.Length);

                if (targetMap.Count(entity => entity.A != 0) > 0)
                    erroded = true;
            }

            return erroded;
        }


        //public virtual bool Erroded(Base2DObject obj, bool errodBlock = false)
        //{
        //    bool eroded = false;

        //    int xCol = (int)Math.Abs((Position.X - halfWidth) - (obj.Position.X - obj.halfWidth));
        //    int yRow = (int)Math.Abs((Position.Y - halfHeight) - (obj.Position.Y - obj.halfHeight));

        //    Vector2 dir = obj.Position - Position;

        //    #region X
        //    if (dir.X > 0)
        //        xCol = Width - (int)((Position.X + halfWidth) - (obj.Position.X - obj.halfWidth)) - 1;

        //    if (dir.X < 0)
        //        xCol = (int)MathHelper.Clamp((obj.Position.X - obj.halfWidth) - (Position.X - halfWidth), 0, Width);

        //    if (dir.X == 0)
        //        xCol = halfWidth - obj.halfWidth;

        //    #endregion

        //    #region Y

        //    if (dir.Y > 0)
        //        yRow = Height - (int)((Position.Y + halfHeight) - (obj.Position.Y - obj.halfHeight)) - 1;

        //    if (dir.Y < 0)
        //        yRow = (int)MathHelper.Clamp((obj.Position.Y - obj.halfHeight) - (Position.Y - halfHeight), 0, Height);

        //    if (dir.Y == 0)
        //        yRow = halfHeight - obj.halfHeight;

        //    if (xCol < 0)
        //        xCol = 0;
        //    if (yRow < 0)
        //        yRow = 0;

        //    #endregion

        //    if (xCol >= 0 && xCol <= Texture2D.Width - 1 && yRow >= 0 && yRow <= Texture2D.Height - 1)
        //    {
        //        int w = Texture2D.Width - xCol;
        //        if (w > obj.Width)
        //            w = obj.Width;
        //        int h = Texture2D.Height - yRow;
        //        if (h > obj.Height)
        //            h = obj.Height;

        //        if (h <= 0)
        //            h = 1;
        //        if (w <= 0)
        //            w = 1;

        //        if (w > Texture2D.Width - 1)
        //            w = Texture2D.Width - 1;

        //        if (h > Texture2D.Height - 1)
        //            h = Texture2D.Height - 1;

        //        Rectangle targetPixels = new Rectangle(xCol, yRow, w, h);
        //        Color[] targetMap = new Color[w * h];
        //        Texture2D.SetData<Color>(0, targetPixels, targetMap, 0, targetMap.Length);
        //        eroded = true;
        //    }

        //    return eroded;
        //}

        public virtual void Draw(GameTime gameTime)
        {
            if(Game.RenderAllBalck)
                Game.spriteBatch.Draw(Texture2D, RenderRectangle, null, Color.Black, Rotation, Origin, SpriteEffects.None, LayerDepth);
            else
                Game.spriteBatch.Draw(Texture2D, RenderRectangle, null, Color, Rotation, Origin, SpriteEffects.None, LayerDepth);
        }

        public virtual void Collided(CollisionData collisionData)
        {
            if (OnCollided != null)
                OnCollided(collisionData);
        }
    }
}
